package com.atguigu.tingshu.search.service.impl;

import co.elastic.clients.elasticsearch.ElasticsearchClient;
import co.elastic.clients.elasticsearch._types.FieldValue;
import co.elastic.clients.elasticsearch._types.SortOrder;
import co.elastic.clients.elasticsearch._types.aggregations.Aggregate;
import co.elastic.clients.elasticsearch._types.query_dsl.BoolQuery;
import co.elastic.clients.elasticsearch._types.query_dsl.ChildScoreMode;
import co.elastic.clients.elasticsearch.core.SearchRequest;
import co.elastic.clients.elasticsearch.core.SearchResponse;
import co.elastic.clients.elasticsearch.core.search.HitsMetadata;
import co.elastic.clients.elasticsearch.core.search.Suggestion;
import com.alibaba.fastjson.JSONObject;
import com.atguigu.tingshu.album.client.AlbumInfoFeignClient;
import com.atguigu.tingshu.album.client.CategoryFeignClient;
import com.atguigu.tingshu.common.constant.SystemConstant;
import com.atguigu.tingshu.model.album.AlbumInfo;
import com.atguigu.tingshu.model.album.BaseCategory3;
import com.atguigu.tingshu.model.album.BaseCategoryView;
import com.atguigu.tingshu.model.search.AlbumInfoIndex;
import com.atguigu.tingshu.model.search.SuggestIndex;
import com.atguigu.tingshu.model.user.UserInfo;
import com.atguigu.tingshu.query.search.AlbumIndexQuery;
import com.atguigu.tingshu.search.service.SearchService;
import com.atguigu.tingshu.user.client.UserInfoFeignClient;
import com.atguigu.tingshu.vo.album.AlbumStatVo;
import com.atguigu.tingshu.vo.search.AlbumInfoIndexVo;
import com.atguigu.tingshu.vo.search.AlbumSearchResponseVo;
import com.atguigu.tingshu.vo.user.UserInfoVo;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.stereotype.Service;

import java.io.IOException;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.stream.Collectors;
import java.util.stream.Stream;


/**
 * @author sun
 */
@Slf4j
@Service
public class SearchServiceImpl implements SearchService {

    @Resource
    private CategoryFeignClient categoryFeignClient;

    @Resource
    private ElasticsearchClient elasticsearchClient;

    @Resource
    private AlbumInfoFeignClient albumInfoFeignClient;

    @Resource
    private UserInfoFeignClient userInfoFeignClient;

    @Resource
    private ThreadPoolExecutor threadPoolExecutor;

    /**
     * <p>首页频道数据的查询</p>
     * <p>1、先获得一级分类下的7个热门分类</p>
     * <p>2、在es中查询这些分类的专辑数据</p>
     *
     * @param category1Id 一级分类id
     * @return 频道数据
     */
    @Override
    public List<JSONObject> channel(Long category1Id) {
        // 1、先获得一级分类下的7个热门分类
        List<BaseCategory3> topBaseCategory3 = categoryFeignClient.findTopBaseCategory3(category1Id);
        if (topBaseCategory3 == null || topBaseCategory3.isEmpty()) {
            log.error("一级分类{}下没有三级分类", category1Id);
            return null;
        }
        Map<Long, BaseCategory3> baseCategory3Map = topBaseCategory3.stream()
                .collect(Collectors.toMap(BaseCategory3::getId, baseCategory3 -> baseCategory3));
        // 2、在es中查询这些分类的专辑数据
        SearchRequest.Builder builder = getBuilder(topBaseCategory3);
        try {
            SearchResponse<AlbumInfoIndex> searchResponse = elasticsearchClient.search(builder.build(), AlbumInfoIndex.class);
            Map<String, Aggregate> aggregations = searchResponse.aggregations();
            if (aggregations != null) {
                return aggregations.get("aggCategory3Id")
                        .lterms()
                        .buckets()
                        .array()
                        .stream()
                        .map(longTermsBucket1 -> {
                            JSONObject c3Json = new JSONObject();
                            c3Json.put("baseCategory3", baseCategory3Map.get(longTermsBucket1.key()));
                            List<AlbumInfoIndex> top6Album = null;
                            if (longTermsBucket1.aggregations() != null) {
                                top6Album = longTermsBucket1.aggregations()
                                        .get("top6Album")
                                        .topHits()
                                        .hits()
                                        .hits()
                                        .stream()
                                        .map(hit1 -> {
                                            AlbumInfoIndex albumInfoIndex = null;
                                            if (hit1.source() != null) {
                                                albumInfoIndex = hit1.source().to(AlbumInfoIndex.class);
                                            }
                                            return albumInfoIndex;
                                        })
                                        .toList();
                            }
                            c3Json.put("list", top6Album);
                            return c3Json;
                        })
                        .toList();
            }
            return null;
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * 构建查询条件
     *
     * @param topBaseCategory3 一级分类下的前7个热门分类
     * @return 查询条件
     */
    private static SearchRequest.Builder getBuilder(List<BaseCategory3> topBaseCategory3) {
        SearchRequest.Builder builder = new SearchRequest.Builder();
        builder.index("albuminfo");
        // 查询条件
        builder.query(
                query1 -> query1.terms(
                        terms1 -> terms1.field("category3Id")
                                .terms(
                                        terms2 -> terms2.value(
                                                topBaseCategory3.stream().map(
                                                        baseCategory3 -> FieldValue.of(baseCategory3.getId())).toList()
                                        ))));
        // 聚合
        builder.aggregations(
                "aggCategory3Id",
                aggregation1 -> aggregation1.terms(
                                terms1 -> terms1.field("category3Id"))
                        // 子聚合
                        .aggregations("top6Album",
                                subAggregation1 -> subAggregation1.topHits(
                                        topHits1 -> topHits1.size(6)
                                                .sort(sort1 -> sort1.field(
                                                        fieldSort1 -> fieldSort1.field("playStatNum")
                                                                .order(SortOrder.Desc))))));
        return builder;
    }

    /**
     * 专辑搜索
     *
     * @param albumIndexQuery 专辑搜索条件
     * @return 专辑搜索结果
     */
    @Override
    public AlbumSearchResponseVo search(AlbumIndexQuery albumIndexQuery) {
        // 拼接条件
        SearchRequest searchRequest = buildSearchRequest(albumIndexQuery);
        try {
            // 执行搜索
            SearchResponse<AlbumInfoIndex> searchResponse = elasticsearchClient.search(searchRequest, AlbumInfoIndex.class);
            // 解析结果
            AlbumSearchResponseVo albumSearchResponseVo = getSearchResult(searchResponse);

            Integer pageSize = albumIndexQuery.getPageSize();
            Long total = albumSearchResponseVo.getTotal();

            albumSearchResponseVo.setPageNo(albumIndexQuery.getPageNo());
            albumSearchResponseVo.setPageSize(pageSize);
            albumSearchResponseVo.setTotalPages((total % pageSize) == 0 ?
                    total / pageSize :
                    total / pageSize + 1);

            // 返回
            return albumSearchResponseVo;
        } catch (IOException e) {
            throw new RuntimeException(e);
        }


    }


    /**
     * 解析结果
     *
     * @param searchResponse 搜索结果
     * @return 专辑搜索结果
     */
    private AlbumSearchResponseVo getSearchResult(SearchResponse<AlbumInfoIndex> searchResponse) {

        HitsMetadata<AlbumInfoIndex> hits = searchResponse.hits();

        long total = 0;
        if (hits.total() != null) {
            total = hits.total().value();
        }

        List<AlbumInfoIndexVo> albumInfoIndexVos = hits.hits().stream().map(hit1 -> {
            AlbumInfoIndexVo albumInfoIndexVo = new AlbumInfoIndexVo();
            AlbumInfoIndex albumInfoIndex = hit1.source();
            if (albumInfoIndex != null) {
                // 高亮
                hit1.highlight().forEach((key, value) -> {
                    switch (key) {
                        case "albumTitle":
                            StringBuilder stringBuilder1 = new StringBuilder();
                            value.forEach(stringBuilder1::append);
                            albumInfoIndex.setAlbumTitle(stringBuilder1.toString());
                            break;
                        case "albumIntro":
                            StringBuilder stringBuilder2 = new StringBuilder();
                            value.forEach(stringBuilder2::append);
                            albumInfoIndex.setAlbumIntro(stringBuilder2.toString());
                            break;
                        default:
                            log.error("未知高亮字段：{}", key);
                            throw new RuntimeException("未知高亮字段：" + key);
                    }
                });
                // 拷贝
                BeanUtils.copyProperties(albumInfoIndex, albumInfoIndexVo);
            }
            return albumInfoIndexVo;
        }).toList();

        return AlbumSearchResponseVo
                .builder()
                .total(total)
                .list(albumInfoIndexVos)
                .build();
    }


    /**
     * <p>拼接查询条件</p>
     *
     * @param albumIndexQuery 查询条件
     * @return 搜索请求
     */
    private SearchRequest buildSearchRequest(AlbumIndexQuery albumIndexQuery) {
        SearchRequest.Builder builder = new SearchRequest.Builder();
        builder.index("albuminfo");

        // 构建组合条件查询对象
        BoolQuery.Builder boolQueryBuilder = new BoolQuery.Builder();
        boolean flag = false;

        // 关键字搜索 -- 匹配 专辑的title和专辑介绍
        String keyword = albumIndexQuery.getKeyword();
        if (StringUtils.isNoneBlank(keyword)) {
            flag = true;
            boolQueryBuilder
                    .should(query1 -> query1.match(
                            match1 -> match1.field("albumTitle")
                                    .query(keyword)
                    ))
                    .should(query1 -> query1.match(
                            match1 -> match1.field("albumIntro")
                                    .query(keyword)
                    ));
        }

        // 一级分类
        Long category1Id = albumIndexQuery.getCategory1Id();
        if (category1Id != null) {
            flag = true;
            boolQueryBuilder.must(query1 -> query1.term(
                    term1 -> term1.field("category1Id")
                            .value(category1Id)
            ));
        }
        // 二级分类
        Long category2Id = albumIndexQuery.getCategory2Id();
        if (category2Id != null) {
            flag = true;
            boolQueryBuilder.must(query1 -> query1.term(
                    term1 -> term1.field("category2Id")
                            .value(category2Id)
            ));
        }
        // 三级分类
        Long category3Id = albumIndexQuery.getCategory3Id();
        if (category3Id != null) {
            flag = true;
            boolQueryBuilder.must(query1 -> query1.term(
                    term1 -> term1.field("category3Id")
                            .value(category3Id)
            ));
        }
        // 标签
        List<String> attributeList = albumIndexQuery.getAttributeList();
        if (attributeList != null && !attributeList.isEmpty()) {
            flag = true;
            attributeList.forEach(
                    attribute -> {
                        String[] split = attribute.split(":");
                        if (split.length == 2) {
                            boolQueryBuilder.must(query1 -> query1.nested(
                                            nested1 -> nested1.path("attributeValueIndexList")
                                                    .query(nestQuery1 -> nestQuery1.term(
                                                            term1 -> term1.field("attributeValueIndexList.attributeId")
                                                                    .value(split[0])
                                                    ))
                                                    .query(nestQuery2 -> nestQuery2.term(
                                                            term1 -> term1.field("attributeValueIndexList.valueId")
                                                                    .value(split[1])
                                                    ))
                                                    .scoreMode(ChildScoreMode.Sum)
                                    )
                            );
                        } else {
                            log.error("未知标签：{}", attribute);
                            throw new RuntimeException("未知标签：" + attribute);
                        }
                    }
            );
        }

        // 构建查询对象
        builder.query(boolQueryBuilder.build()._toQuery());

        if (!flag) {
            builder.query(
                    query1 -> query1.matchAll(
                            match1 -> match1.boost(1.0F)
                    )
            );
        }

        //分页
        builder.from((albumIndexQuery.getPageNo() - 1) * albumIndexQuery.getPageSize());
        builder.size(albumIndexQuery.getPageSize());
        // 排序
        String order = albumIndexQuery.getOrder();
        // 综合排序 默认   1
        // 播放量         2
        // 创建时间        3
        if (StringUtils.isNotEmpty(order)) {
            String[] split = order.split(":");
            switch (split[0]) {
                case "1":
                    builder.sort(sort1 -> sort1.field(
                            fieldSort1 -> fieldSort1.field("id")
                                    .order("asc".equals(split[1]) ? SortOrder.Asc : SortOrder.Desc)));
                    break;
                case "2":
                    builder.sort(sort1 -> sort1.field(
                            fieldSort1 -> fieldSort1.field("playStatNum")
                                    .order("asc".equals(split[1]) ? SortOrder.Asc : SortOrder.Desc)));
                    break;
                case "3":
                    builder.sort(sort1 -> sort1.field(
                            fieldSort1 -> fieldSort1.field("createTime")
                                    .order("asc".equals(split[1]) ? SortOrder.Asc : SortOrder.Desc)));
                    break;
                default:
                    builder.sort(sort1 -> sort1.field(
                            fieldSort1 -> fieldSort1.field("id")
                                    .order(SortOrder.Desc)));
            }
        }
        // 高亮
        builder.highlight(
                highlight2 -> highlight2
                        .fields("albumIntro",
                                highlightField1 -> highlightField1.preTags("<span style='color:blue'>")
                                        .postTags("</span>"))
                        .fields("albumTitle",
                                highlightField1 -> highlightField1.preTags("<span style='color:blue'>")
                                        .postTags("</span>"))
        );

        return builder.build();
    }


    /**
     * 根据关键词给出搜索建议
     *
     * @param key 关键词
     * @return 搜索建议
     */
    @Override
    public List<String> completeSuggest(String key) {
        // 判断是否为空
        if (StringUtils.isEmpty(key)) {
            return List.of();
        }
        // 请求对象
        SearchRequest searchRequest = getSearchRequest(key);
        try {
            // 搜索提示词
            SearchResponse<SuggestIndex> searchResponse = elasticsearchClient.search(searchRequest, SuggestIndex.class);

            // 解析结果
            Map<String, List<Suggestion<SuggestIndex>>> suggest = searchResponse.suggest();
            if (suggest != null) {
                List<String> list1 = suggest.get("suggestKeyword").getFirst().completion().options().stream().map(
                        option -> {
                            if (option.source() != null) {
                                return option.source().getTitle();
                            }
                            return null;
                        }).toList();

                List<String> list2 = suggest.get("suggestKeywordPinyin").getFirst().completion().options().stream().map(
                        option -> {
                            if (option.source() != null) {
                                return option.source().getTitle();
                            }
                            return null;
                        }).toList();
                List<String> list3 = suggest.get("suggestKeywordSequence").getFirst().completion().options().stream().map(
                        option -> {
                            if (option.source() != null) {
                                return option.source().getTitle();
                            }
                            return null;
                        }).toList();

                return Stream.of(list1, list2, list3).flatMap(Collection::stream).distinct().toList();
            }
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
        return List.of();
    }


    /**
     * 获取搜索建议请求对象
     *
     * @param key 搜索关键词
     * @return 请求对象
     */
    private static SearchRequest getSearchRequest(String key) {
        SearchRequest.Builder builder = new SearchRequest.Builder();
        builder.index("suggest_info");
        // 构建条件
        builder.suggest(
                suggest1 -> suggest1
                        .suggesters(
                                "suggestKeyword",
                                fieldSuggest1 -> fieldSuggest1
                                        .prefix(key)
                                        .completion(
                                                completion1 -> completion1
                                                        .field("keyword")
                                                        .skipDuplicates(true)
                                                        .fuzzy(fuzzy1 -> fuzzy1.fuzziness("auto"))
                                        )
                        ).suggesters(
                                "suggestKeywordPinyin",
                                fieldSuggest1 -> fieldSuggest1
                                        .prefix(key)
                                        .completion(
                                                completion1 -> completion1
                                                        .field("keywordPinyin")
                                                        .skipDuplicates(true)
                                                        .fuzzy(fuzzy1 -> fuzzy1.fuzziness("auto"))
                                        )
                        ).suggesters(
                                "suggestKeywordSequence",
                                fieldSuggest1 -> fieldSuggest1
                                        .prefix(key)
                                        .completion(
                                                completion1 -> completion1
                                                        .field("keywordSequence")
                                                        .skipDuplicates(true)
                                                        .fuzzy(fuzzy1 -> fuzzy1.fuzziness("auto"))
                                        )
                        )
        );

        return builder.build();
    }


    /**
     * 获取专辑详情
     *
     * @param albumId 专辑id
     * @return 专辑详情
     */
    @Override
    public Map<String, Object> getAlbumDetails(Long albumId) {
        ConcurrentHashMap<String, Object> concurrentHashMap = new ConcurrentHashMap<>();

        CompletableFuture<AlbumInfo> future1 = CompletableFuture.supplyAsync(() -> {
                    // 专辑数据
                    AlbumInfo albumInfo = albumInfoFeignClient.getAlbumInfo(albumId);
                    if (albumInfo == null || albumInfo.getId() == null) {
                        // TODO 专辑不存在
                        throw new RuntimeException("专辑不存在");
                    }
                    concurrentHashMap.put("albumInfo", albumInfo);
                    return albumInfo;
                },
                threadPoolExecutor);
        CompletableFuture<Void> future2 = future1.thenRunAsync(() -> {
                    //  专辑统计信息
                    Map<String, Integer> albumStats = albumInfoFeignClient.getAlbumStats(albumId);
                    concurrentHashMap.put("albumStatVo", AlbumStatVo.builder()
                            .albumId(albumId)
                            .playStatNum(albumStats.get(SystemConstant.ALBUM_STAT_PLAY))
                            .subscribeStatNum(albumStats.get(SystemConstant.ALBUM_STAT_SUBSCRIBE))
                            .buyStatNum(albumStats.get(SystemConstant.ALBUM_STAT_BROWSE))
                            .commentStatNum(albumStats.get(SystemConstant.ALBUM_STAT_COMMENT))
                            .build());
                },
                threadPoolExecutor);

        CompletableFuture<Void> future3 = future1.thenAcceptAsync(
                albumInfo -> {
                    // 专辑作者信息
                    UserInfo userInfo = userInfoFeignClient.getUserInfoById(albumInfo.getUserId());
                    UserInfoVo userInfoVo = new UserInfoVo();
                    BeanUtils.copyProperties(userInfo, userInfoVo);
                    concurrentHashMap.put("announcer", userInfoVo);
                },
                threadPoolExecutor);


        CompletableFuture<Void> future4 = future1.thenAcceptAsync(
                albumInfo -> {
                    // 专辑分类信息
                    BaseCategoryView categoryView = categoryFeignClient.getCategoryView(albumInfo.getCategory3Id());
                    concurrentHashMap.put("baseCategoryView", categoryView);
                },
                threadPoolExecutor
        );

        CompletableFuture.allOf(future1, future2, future3, future4).handle(
                (aVoid, throwable) -> {
                    if (throwable != null) {
                        log.error(throwable.getMessage());
                    }
                    return null;
                }
        ).join();
        return concurrentHashMap;
    }

}
